﻿/*
 * OpenDoPE Word Add-In
 * Copyright (C) 2010-2011  Plutext Pty Ltd
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * For details, see http://www.gnu.org/licenses/gpl-2.0.html
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NLog;

using Word = Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
//using DocumentFormat.OpenXml.Wordprocessing;




namespace OpenDoPE_Wed
{
    /// <summary>
    /// This is the user control for my custom task pane.
    /// 
    /// The idea is that it will show 4 different views:
    /// 
    /// 1. not in a content control (tell them their not, point to dev tab to add)
    /// 2. in a virgin control
    /// 3. in a bind or a repeat
    /// 4. in a condition
    /// 
    /// We have a separate user control for each of these.
    /// 
    /// This contents will be clear() and the relevant control added
    /// as the user moves around the document.
    /// 
    /// </summary>
    public partial class WedTaskPane : UserControl
    {
        static Logger log = LogManager.GetLogger("OpenDoPE_Wed");

        // CustomXMLPart.BuiltIn
        //const string CORE_PROPERTIES_STOREITEMID = "{6C3C8BC8-F283-45AE-878A-BAB7291924A1}";
        //const string EXTENDED_PROPERTIES_STOREITEMID = "{6668398D-A668-4E3E-A5EB-62B293D839F1}";
        //const string COVERPAGE_PROPERTIES_STOREITEMID = "{55AF091B-3C7A-41E3-B477-F2FDAA23CFDA}";

        public Word.ContentControl currentCC { get; set; }

        public Microsoft.Office.Tools.CustomTaskPane ctp { get; set; }

        /// <summary>
        /// The document being edited.  TODO: use this more
        /// (ie instead of active document)
        /// </summary>
        public Word.Document associatedDocument { get; set; }

        public bool questions { get; set; }

        //OpenDoPE_Wed.XP xp;
        public List<Office.CustomXMLPart> cxp { get; set; }
        public Office.CustomXMLPart activeCxp { get; set; }


        public Office.CustomXMLPart xpathsPart { get; set; }
        public Office.CustomXMLPart conditionsPart { get; set; }
        public Office.CustomXMLPart questionsPart { get; set; }
        public Office.CustomXMLPart componentsPart { get; set; }

        public Office.CustomXMLPart partBeingEdited; // could be cxp or conditions

        public WedTaskPane(
            List<Office.CustomXMLPart> cxp,
            Office.CustomXMLPart xpathsPart,
            Office.CustomXMLPart conditionsPart,
            Office.CustomXMLPart questionsPart,
            Office.CustomXMLPart componentsPart
            )
        {
            //InitializeComponent();
            
            // Add scroll bars if there isn't enough room for the form
            this.AutoScroll = true;


            if (questionsPart == null)
            {
                questions = false;
            }
            else
            {
                questions = true;
            }

            // TODO - remove this
            //XmlEditorControl af = new XmlEditorControl(cxp[0], ".xml", true);
            //af.Size = new System.Drawing.Size(350, 600);
            //this.Controls.Add(af);

            this.cxp = cxp;
            activeCxp = cxp[0];
            this.xpathsPart = xpathsPart;
            this.conditionsPart = conditionsPart;
            this.questionsPart = questionsPart;
            this.componentsPart = componentsPart;

            //Office.CustomXMLNode node = cxp.SelectSingleNode("/node()");
            //xp = new OpenDoPE_Wed.XP(node.XML);

            partBeingEdited = activeCxp;

            // setupCcEvents(Globals.ThisAddIn.Application.ActiveDocument);

            //timer = new System.Threading.Timer(timerCallback, this, SEC_1, SEC_1);


        }

/*
        public void setupCcEvents(Word.Document document)
        {

            document.ContentControlOnEnter
                += new Word.DocumentEvents2_ContentControlOnEnterEventHandler(document_ContentControlOnEnter);
*/
            /* Content control event handlers don't fire (Word 2007)
             * 
             * Using a simple form as a CTP isn't a problem.
             * But the CTP can't contain the line:
             * 
             *     System.Windows.Forms.Application.DoEvents();
             *     
             * The SharpDevelop stuff sitting behind my xml editor possibly 
             * does this (35 occurrences of doevents, but none appeared to be
             * in relevant files).
             * 
             * 
             * NB, CXP ReplaceChildSubtree also seems to kill Event handlers dead?
             * 
             */
/*
            document.ContentControlAfterAdd += new Word.DocumentEvents2_ContentControlAfterAddEventHandler(document_ContentControlAfterAdd);
        }

        void document_ContentControlAfterAdd(Word.ContentControl NewContentControl, bool InUndoRedo)
        {
            log.Debug("a cc was added");

        }
    */

        void handleStateChange()
        {

            this.Controls.Clear();

            if (currentCC == null)
            {
                // TODO
                log.Info(".. not in a cc");
                return;
            }

            log.Info("Entered content control ..");
            //this.currentCC = cc;

            partBeingEdited = activeCxp;


            TagData td = new TagData(currentCC.Tag);

            if (currentCC.XMLMapping.IsMapped
                || td.get("od:xpath") != null )
            {
                // currentCC.XMLMapping.IsMapped returns FALSE
                // if there IS a databinding element, but it
                // returns boolean, rather than an XML element.
                // Hence test for od:xpath.
                // Really, we could just test for od:xpath.


                // If its bound to core props or extended props, or
                // cover page props
                if (currentCC.XMLMapping.CustomXMLPart.BuiltIn)
                {
                    MessageBox.Show("This content control was bound using a BuildingBlock.  To bind it to something else, delete it and create a new one." + currentCC.XMLMapping.CustomXMLPart.Id);
                }
                else
                {
                    displayBindForm(false);
                }


            } else if (td.get("od:condition") != null) {

                displayConditionForm();

            }
            else if (td.get("od:repeat") != null)
            {

                displayRepeatForm();
            }
            else if (td.get("od:component") != null)
            {

                displayComponentForm();
            }
            else
            {
                log.Debug(".. not mapped.");

                SdtUndoped undoped = new SdtUndoped(currentCC);
                this.Controls.Add(undoped);

            }

        }


        public void displayBindForm(bool editor)
        {
            this.Controls.Clear();
            uiCtrlSimpleConditionMaker = null; // null out the other form
            repeatButtons = null;
            //this.InitializeComponent();

            // Show sdt properties - common to all forms
            this.sdtDetails = new UserControlSdtDetails();
            sdtDetails.populate(currentCC);
            this.Controls.Add(sdtDetails);

            if (editor)
            {
                if (cxp.Count > 1)
                {
                    addPartsListBox();
                }


                addXmlEditor(activeCxp, ".xml", true);

                bindButtons = new UserControlBindButtons(this);
                if (bindButtons.ensurePlainText(currentCC))
                {
                    bindButtons.Location = new Point(10, 540);
                    this.Controls.Add(bindButtons);
                }
            }
            else
            {
                UserControlBoundInfo info = new UserControlBoundInfo(this);
                info.Location = new Point(10, 150);
                info.labelValue.Text = currentCC.XMLMapping.XPath;
                this.Controls.Add(info);

            }

            this.Refresh();
        }

        
        
        public void displayRepeatForm()
        {
            this.Controls.Clear();
            uiCtrlSimpleConditionMaker = null; // null out the other form
            bindButtons = null;
            //this.InitializeComponent();

            // Show sdt properties - common to all forms
            this.sdtDetails = new UserControlSdtDetails();
            sdtDetails.populate(currentCC);
            this.Controls.Add(sdtDetails);

            if (cxp.Count > 1)
            {
                addPartsListBox();
            }

            addXmlEditor(activeCxp, ".xml", true);

            // Populate existing values, if any
            TagData td = new TagData(currentCC.Tag);
            populateXPathInfo(td.get("od:repeat"));

            repeatButtons = new UserControlRepeatButtons(this);
            repeatButtons.Location = new Point(10, 540);
            this.Controls.Add(repeatButtons);

        }

        public void populateXPathInfo(string xpathid)
        {

            if (xpathid != null)
            {
                xmlEditor.textBoxXPathID.Text = xpathid;

                // Find the relevant xpath
                xpaths xpaths = new xpaths();
                xpaths.Deserialize(xpathsPart.XML, out xpaths);

                foreach (xpathsXpath xp in xpaths.xpath)
                {

                    if (xp.id.Equals(xpathid))
                    {
                        xmlEditor.xpathTextBox.Text = xp.dataBinding.xpath;
                        break;
                    }
                }
            }

        }

        public void addPartsListBox()
        {
            this.listParts = new ListBox();
            this.listParts.Location = new Point(10, 125);
            this.listParts.Size = new System.Drawing.Size(200, 25); // width, height
            this.Controls.Add(listParts);

            foreach (Office.CustomXMLPart p in cxp) {

                Office.CustomXMLNode node = p.SelectSingleNode("/node()");
                listParts.Items.Add(node.BaseName);
            }
            listParts.SelectedIndex = 0;
            listParts.SelectedIndexChanged += new EventHandler(listParts_SelectedIndexChanged);

        }

        void listParts_SelectedIndexChanged(object sender, EventArgs e)
        {
            foreach (Office.CustomXMLPart p in cxp)
            {

                Office.CustomXMLNode node = p.SelectSingleNode("/node()");

                if ((string)listParts.SelectedItem == node.BaseName)
                {
                    if (activeCxp != p)
                    {
                        // Save changes to current part
                        if (xmlEditor.isWellFormed())
                        {
                            xmlEditor.commitChanges();
                        }
                        else
                        {
                            // TODO revert to previous index

                            return;
                        }

                        // Use this part in the editor instead.
                        activeCxp = p;

                        // which form to re-build? 
                        if (uiCtrlSimpleConditionMaker != null)
                        {
                            displaySimpleConditionMakerForm();
                        }
                        else if (repeatButtons!=null)
                        {
                            displayRepeatForm();
                        }
                        else {
                            displayBindForm(true); // ie with editor
                        }

                    }
                    return;
                }
            }

        }

        public void addXmlEditor(Office.CustomXMLPart part, string ext, bool displayXPath)
        {
            int editorWidth = ctp.Width - 50;
            if (editorWidth < 600) editorWidth = 600; // there's no magic to this number,
                // but since we have a horizontal scrollbar if there's not enough room,
                // may as well make the editor a comfortable width

            this.xmlEditor = new XmlEditorControl(part, ext, displayXPath, editorWidth);
            this.xmlEditor.Location = new Point(10, 150);
            this.xmlEditor.Size = new System.Drawing.Size(editorWidth, 400); // width, height
            this.Controls.Add(xmlEditor);

            
        }

        public void displayConditionForm()
        {
            this.Controls.Clear();
            uiCtrlSimpleConditionMaker = null; // null out the other form

            // Show sdt properties - common to all forms
            this.sdtDetails = new UserControlSdtDetails();
            sdtDetails.populate(currentCC);
            this.Controls.Add(sdtDetails);

            addXmlEditor(conditionsPart, ".con", false);

            conditionButtons = new UserControlConditionButtons(this);
            conditionButtons.Location = new Point(10, 540);
            this.Controls.Add(conditionButtons);

            conditionButtons.initFields();

        }

        public void displayComponentForm()
        {
            this.Controls.Clear();
            //uiCtrlSimpleConditionMaker = null; // null out the other form

            // Show sdt properties - common to all forms
            this.sdtDetails = new UserControlSdtDetails();
            sdtDetails.populate(currentCC);
            this.Controls.Add(sdtDetails);

            this.componentUC = new UserControlComponentIRI(this);
            componentUC.Location = new Point(10, 150);
            this.Controls.Add(componentUC);
            componentUC.initFields();

        }


        public void displaySimpleConditionMakerForm()
        {
            this.Controls.Clear();
            conditionButtons = null; // null out the other forms
            repeatButtons = null;

            // Show sdt properties - common to all forms
            this.sdtDetails = new UserControlSdtDetails();
            sdtDetails.populate(currentCC);
            this.Controls.Add(sdtDetails);

            if (cxp.Count > 1)
            {
                addPartsListBox();
            }

            addXmlEditor(activeCxp, ".xml", true);

            uiCtrlSimpleConditionMaker = new UiCtrlSimpleConditionMaker(this);
            uiCtrlSimpleConditionMaker.Location = new Point(10, 520);
            this.Controls.Add(uiCtrlSimpleConditionMaker);
        }


/*
 *  The  problem with using a timer to detect when we enter/leave a 
 *  CC is that when you click on the task pane, the selection
 *  is no longer in the content control, so the state change
 *  action triggers again.
 *  
 *  The WindowSelectionChange event works better.
 *  
 * 
        private static int SEC_1 = 1000;
        private static System.Threading.Timer timer;

        public static void timerCallback(object state)
        {
            log.Debug("timer thread .. in cc?");

            Word.ContentControl cc = getActiveContentControl();

            if (cc!=((WedTaskPane)state).currentCC)  {
                log.Debug("state changed!");

                // Can't just do ((WedTaskPane)state).handleStateChange(cc);
                // since this is not the UI thread.
                ((WedTaskPane)state).currentCC = cc;
                ((WedTaskPane)state).Invoke(
                    new MethodInvoker(
                        ((WedTaskPane)state).handleStateChange)); 
            }
        }
 */

        public void saveXmlEditorContents()
        {
            if (this.xmlEditor != null
                && this.xmlEditor.isDirty()
                && this.xmlEditor.isWellFormed())
            {
                log.Debug("Saving the user's xml");
                this.xmlEditor.commitChanges();
            }
            else
            {
                log.Debug("No need to save the user's xml");
            }

        }

        public void handleSelectionChange()
        {
            saveXmlEditorContents();

            Word.ContentControl cc = getActiveContentControl();

            if (cc!=currentCC)  {
                log.Debug("state changed!");
                currentCC = cc;
                handleStateChange();
            }
        }

        // Return the content control containing the cursor, or
        // null if we are outside a content control or the selection
        // spans the content control boundary
        public static Word.ContentControl getActiveContentControl()
        {
            Word.Selection selection = Globals.ThisAddIn.Application.Selection;

            // Word.ContentControls ccs = selection.ContentControls;
            // only has a value if the selection contains an entire ContentControl

            // so how do you expand a selection to include the entire content control?
            // or can we ask whether a content control is active,
            // ie the selection is in it?

            // or iterate through the content controls in the active doc,
            // asking whether their range contains my range? YES
            // Hmmm... what about nesting of cc?

            // First try MDP
            object missing = System.Type.Missing;
            Word.ContentControl result = getActiveContentControl(Globals.ThisAddIn.Application.ActiveDocument.Range(ref missing, ref missing));

            if (result!=null) return result;

            // Try other parts (eg headers/footers, comments, footnote/endnote)
            foreach (Word.Range storyRange in Globals.ThisAddIn.Application.ActiveDocument.StoryRanges)
            {
                // TODO: don't repeat MDP

                result = getActiveContentControl(storyRange);

                if (result != null) return result;

            }

            //if (stateDocx.InControl == true)

            return null;

        }

        public static Word.ContentControl getActiveContentControl(Word.Range storyRange)
        {
            Word.Selection selection = Globals.ThisAddIn.Application.Selection;

            // Word.ContentControls ccs = selection.ContentControls;
            // only has a value if the selection contains an entire ContentControl

            // so how do you expand a selection to include the entire content control?
            // or can we ask whether a content control is active,
            // ie the selection is in it?

            // or iterate through the content controls in the active doc,
            // asking whether their range contains my range? YES
            // Hmmm... what about nesting of cc?

            Word.ContentControl result = null;

            foreach (Word.ContentControl ctrl in storyRange.ContentControls)
            {
                int highestCount = -1;
                if (selection.InRange(ctrl.Range))
                {
                    //diagnostics("DEBUG - Got control");
                    log.Debug("In control: " + ctrl.ID);
                    int parents = countParent(ctrl);
                    if (parents>highestCount)
                    {
                        log.Debug(".. highest ");
                        result = ctrl;
                    }
                }
                // else user's selection is totally outside the content control, or crosses its boundary
            }


            return result;

        }

        private static int countParent(Word.ContentControl ctrl)
        {
            int counter = 0;

            while (ctrl.ParentContentControl != null)
            {
                counter++;
                ctrl = ctrl.ParentContentControl;
            }
            log.Debug("= " + counter);
            return counter;
        }

        /// <summary>
        /// Show the document.xml in the XML editor.  Useful for debugging.
        /// </summary>
        public void showDocumentInXmlEditor()
        {
            this.Controls.Clear();

            int editorWidth = ctp.Width - 50;
            if (editorWidth < 600) editorWidth = 600; // there's no magic to this number,
            // but since we have a horizontal scrollbar if there's not enough room,
            // may as well make the editor a comfortable width

            string xml = OpenXmlUtils.getDocumentXml(Globals.ThisAddIn.Application.ActiveDocument);

            log.Debug(xml);

            this.xmlEditor = new XmlEditorControl(System.Text.Encoding.Unicode.GetBytes(xml), ".xml", false, editorWidth);
            this.xmlEditor.Location = new Point(10, 150);
            this.xmlEditor.Size = new System.Drawing.Size(editorWidth, 400); // width, height
            this.Controls.Add(xmlEditor);

            this.Refresh();

        }

    }
}
